package simpledb;

import java.io.*;

/**
 * BTreeRootPtrPage stores the pointer to the root node used in the B+ tree and
 * implements Page Interface that is used by BufferPool
 *
 * @see BufferPool
 */
public class BTreeRootPtrPage implements Page {
	// size of this page
	public final static int PAGE_SIZE = 9;

	private boolean dirty = false;
	private TransactionId dirtier = null;

	private BTreePageId pid;
	private DataInputStream dis;

	private int root; 
	private int rootCategory;
	private int header;

	private byte[] oldData;

	/**
	 * Constructor.
	 * Construct the BTreeRootPtrPage from a set of bytes of data read from
	 * disk.
	 * The format of an BTreeRootPtrPage is an integer for the page number
	 * of the root node, followed by a byte to encode the category of the root page
	 * (either leaf or internal), followed by an integer for the page number
	 * of the first header page
	 */
	public BTreeRootPtrPage(BTreePageId id, byte[] data) throws IOException {
		this.pid = id;
		this.dis = new DataInputStream(new ByteArrayInputStream(data));

		// read in the root pointer
		root = dis.readInt();
		rootCategory = (int) dis.readByte();

		// read in the header pointer
		header = dis.readInt();
		
		setBeforeImage();
	}

	public void setBeforeImage() {
		oldData = getPageData().clone();
	}

	/**
	 * @return the PageId associated with this page.
	 */
	public BTreePageId getId() {
		return pid;
	}

	/**
	 * There is only one instance of a BTreeRootPtrPage per table. This static 
	 * method is separate from getId() in order to maintain the Page interface
	 * @param tableid - the tableid of this table
	 * @return the root pointer page id for the given table
	 */
	public static BTreePageId getId(int tableid) {
		return new BTreePageId(tableid, 0, BTreePageId.ROOT_PTR);
	}

	/**
	 * Generates a byte array representing the contents of this root pointer page.
	 * Used to serialize this root pointer page to disk.
	 * The invariant here is that it should be possible to pass the byte array
	 * generated by getPageData to the BTreeRootPtrPage constructor and have it
	 * produce an identical BTreeRootPtrPage object.
	 *
	 * @return A byte array corresponding to the bytes of this root pointer page.
	 */
	public byte[] getPageData(){
		int len = PAGE_SIZE;
		ByteArrayOutputStream baos = new ByteArrayOutputStream(len);
		DataOutputStream dos = new DataOutputStream(baos);

		// write out the root pointer (page number of the root page)
		try{
			dos.writeInt(root);
		}catch(IOException e){
			e.printStackTrace();
		}

		// write out the category of the root page (leaf or internal)
		try{
			dos.writeByte((byte) rootCategory);
		}catch(IOException e){
			e.printStackTrace();
		}

		// write out the header pointer (page number of the first header page)
		try{
			dos.writeInt(header);
		}catch(IOException e){
			e.printStackTrace();
		}

		try {
			dos.flush();
		}catch(IOException e) {
			e.printStackTrace();
		}

		return baos.toByteArray();
	}

	/**
	 * Static method to generate a byte array corresponding to an empty
	 * BTreeRootPtrPage.
	 * Used to add new, empty pages to the file. Passing the results of
	 * this method to the BTreeRootPtrPage constructor will create a BTreeRootPtrPage with
	 * no valid entries in it.
	 *
	 * @return The returned ByteArray.
	 */
	public static byte[] createEmptyPageData() {
		int len = PAGE_SIZE;
		return new byte[len]; //all 0
	}

	public void markDirty(boolean dirty, TransactionId tid){
		this.dirty = dirty;
		if (dirty) this.dirtier = tid;
	}

	public TransactionId isDirty() {
		if (this.dirty)
			return this.dirtier;
		else
			return null;
	}

	/** Return a view of this page before it was modified
        -- used by recovery */
	public BTreeRootPtrPage getBeforeImage(){
		try {
			return new BTreeRootPtrPage(pid,oldData);
		} catch (IOException e) {
			e.printStackTrace();
			//should never happen -- we parsed it OK before!
			System.exit(1);
		}
		return null;
	}

	/**
	 * Get the id of the root page in this B+ tree
	 * @return the id of the root page
	 */
	public BTreePageId getRootId() {
		if(root == 0) {
			return null;
		}
		return new BTreePageId(pid.getTableId(), root, rootCategory);
	}

	/**
	 * Set the id of the root page in this B+ tree
	 * @param id - the id of the root page
	 * @throws DbException if the id is invalid
	 */
	public void setRootId(BTreePageId id) throws DbException {
		if(id == null) {
			root = 0;
		}
		else {
			if(id.getTableId() != pid.getTableId()) {
				throw new DbException("table id mismatch in setRootId");
			}
			if(id.pgcateg() != BTreePageId.INTERNAL && id.pgcateg() != BTreePageId.LEAF) {
				throw new DbException("root must be an internal node or leaf node");
			}
			root = id.getPageNumber();
			rootCategory = id.pgcateg();
		}
	}

	/**
	 * Get the id of the first header page, or null if none exists
	 * @return the id of the first header page
	 */
	public BTreePageId getHeaderId() {
		if(header == 0) {
			return null;
		}
		return new BTreePageId(pid.getTableId(), header, BTreePageId.HEADER);
	}

	/**
	 * Set the page id of the first header page
	 * @param id - the id of the first header page
	 * @throws DbException if the id is invalid
	 */
	public void setHeaderId(BTreePageId id) throws DbException {
		if(id == null) {
			header = 0;
		}
		else {
			if(id.getTableId() != pid.getTableId()) {
				throw new DbException("table id mismatch in setHeaderId");
			}
			if(id.pgcateg() != BTreePageId.HEADER) {
				throw new DbException("header must be of type BTreePageId.HEADER");
			}
			header = id.getPageNumber();
		}
	}

	/**
	 * Get the page size of root pointer pages
	 * @return the page size
	 */
	public static int getPageSize() {
		return PAGE_SIZE;
	}

}

